#ifndef MCVBASICARRAYS_H
#define MCVBASICARRAYS_H

#include "mcvarray.h"
#include "mcvdatum.h"

namespace MCV
{

#define MCVUNIFORMARRAY 7777

template <class T>
class UniformArray : public Array
{
private:
	T* _dataptr;
	T _ival;
	int _c;
	int _numel;
	int _id;
	FlatArrayInfo _d;
	MemoryManager* _mm;
	Array* _ref;
	int _refct;
	bool _soft;

	void genArrays(int ndims, int* dimsizes, T ival)
	{
		_ival = ival;
		_d.ndims = ndims;
		_d.type = raw_type_id(ival);
		_d.dimsizes = dimsizes;
		_d.dimweights = new int[ndims + 1];

		int prod = 1;
		_d.dimweights[0] = 1;
		for(int i = 1; i <= _d.ndims; ++i)
		{
			prod *= _d.dimsizes[i-1];
			_d.dimweights[i] = prod;
		}

		_numel = _d.dimweights[_d.ndims];
		_d.dimweights[_d.ndims] = 0;
		_c = 0;

		_d.minpos = 0;
		_d.maxpos = _numel - 1;
		_dataptr = new T[_numel];

		for(int i = 0; i < _numel; ++i)
		{
			_dataptr[i] = ival;
		}

		_d.dataptr = (void*)_dataptr;
	}
public:
	UniformArray(int ndims, int* dimsizes, T ival)
	{
		genArrays(ndims, dimsizes, ival);
		_id = -1;
		_mm = NULL;
		_ref = NULL;
		_soft = true;
		_refct = 0;
	}

	UniformArray(std::vector<int> dimsizes, T ival)
	{
		int ndims = dimsizes.size();
		int* rds = new int[ndims];
		for(int i = 0; i < ndims; ++i)
			rds[i] = dimsizes[i];
		genArrays(ndims, rds, ival);
		_id = -1;
		_mm = NULL;
		_ref = NULL;
		_soft = true;
		_refct = 0;
	}

	UniformArray(int ndims, int* dimsizes, int* dimweights, int minp, int maxp, T* data, Array* ref = NULL)
	{
		_d.ndims = ndims;
		_d.dimsizes = dimsizes;
		_d.dimweights = dimweights;
		_c = dimweights[ndims];
		_d.minpos = minp;
		_d.maxpos = maxp;
		_ival = data[minp];
		_d.type = raw_type_id(_ival);
		_ref = ref;
		_dataptr = data;

		if(_ref)
			_ref->incrRefCount();

		int prod = 1;
		for(int i = 0; i < _d.ndims; ++i)
			prod *= _d.dimsizes[i];
		_numel = prod;

		_id = -1;
		_mm = NULL;
		_soft = false;
		_refct = 0;
	}

	~UniformArray()
	{
		if(_ref)
		{
			_ref->decrRefCount();
			_dataptr = NULL;
		}

		delete[] _dataptr;
		delete[] _d.dimsizes;
		delete[] _d.dimweights;
	}

	int type(){return MCVARRAY;}
	int rawType(){return MCVUNIFORMARRAY;}
	int subType(){return _d.type;}
	int getId(){return _id;}
	void setId(int newId){	_id = newId;}
	int numElements(){	return _numel;	}
	int numDims(){	return _d.ndims;	}
	int size(int d){	return _d.dimsizes[d];	}

	std::vector<int> size()
	{
		std::vector<int> ret;
		for(int i = 0; i < _d.ndims; ++i)
			ret.push_back(_d.dimsizes[i]);
		return ret;
	}

	int stype1d(int x)
	{
		return type_id(_dataptr[x * _d.dimweights[0] + _c]);
	}
	int stypeNd(int* coords, int N)
	{
		int pos = _c;
		for(int i = 0; i < _d.ndims && i < N; ++i)
			pos += (coords[i] * _d.dimweights[i]);
		return type_id(_dataptr[pos]);
	}

	Datum get1d(int x)
	{
		return Datum(_dataptr[(x * _d.dimweights[0]) + _c]);
	}
	Datum get2d(int x, int y)
	{
		return Datum(_dataptr[(x * _d.dimweights[0]) + (y * _d.dimweights[1]) +  _c]);
	}

	Datum get3d(int x, int y, int z)
	{
		return Datum(_dataptr[(x * _d.dimweights[0]) + (y * _d.dimweights[1]) + (z * _d.dimweights[2]) +  _c]);
	}

	Datum getNd(int* coords, int N)
	{
		int pos = _c;
		for(int i = 0; i < _d.ndims && i < N; ++i)
			pos += (coords[i] * _d.dimweights[i]);
		return Datum(_dataptr[pos]);
	}

	void set1d(int x, T& val)
	{
		assert(_soft);
		_dataptr[(x * _d.dimweights[0]) + _c] = val;
	}
	void set2d(int x, int y, T& val)
	{
		assert(_soft);
		_dataptr[(x * _d.dimweights[0]) + (y * _d.dimweights[1]) +  _c] = val;
	}
	void set3d(int x, int y, T& val)
	{
		assert(_soft);
		_dataptr[(x * _d.dimweights[0]) + (y * _d.dimweights[1]) + (z * _d.dimweights[2]) +  _c] = val;
	}
	void setNd(int* coords, int N, T& val)
	{
		assert(_soft);
		int pos = _c;
		for(int i = 0; i < _d.ndims && i < N; ++i)
			pos += (coords[i] * _d.dimweights[i]);
		_dataptr[pos] = val;
	}

	void incrRefCount()	{	++_refct;	}
	void decrRefCount()
	{
		--_refct;
		if(_refct <= 0)
		{
			if(_mm)
				_mm->remove(this);
			else
				delete this;
		}
	}

	int refCount(){	return _refct;	}
	virtual bool isFlat(){ return true; }
	FlatArrayInfo* getFlatArray(){		return &_d;	}
	bool soft(){	return _soft;	}
	void harden(){	_soft = false;	}
	const std::string getEncoderType(){	return "FLAT";}
	void setmm(MemoryManager* mm){	_mm = mm;	}
};

class BaseCollection : public Array
{
public:
	// inherited stuff
	int type();
	int rawType();
	int subType();

	int getId();
	void setId(int newId);

	int numElements();
	int numDims();
	int size(int d);
	std::vector<int> size();

	int stype1d(int x);
	int stypeNd(int* coords, int N);

	inline Datum asDatum(int x);

	Datum get1d(int x);
	Datum get2d(int x, int y);
	Datum get3d(int x, int y, int z);
	Datum getNd(int* coords, int N);

	void incrRefCount();
	void decrRefCount();
	int refCount();

	bool isFlat();
	FlatArrayInfo* getFlatArray();

	bool soft();
	const std::string getEncoderType();
	void setmm(MemoryManager* mm);

	// constructors and whatnot
	BaseCollection(int numdims, int* dimsizes);
	BaseCollection(std::vector<int> dims);
	~BaseCollection();

	void harden();
	int p2d(int x, int y);
	int p3d(int x, int y, int z);
	int pNd(int* coords, int N);
	void seti(int p, mcvint val);
	void setf(int p, mcvfloat val);
	void setu(int p, mcvuchar val);
	void sets(int p, std::string val);
	void setd(int p, Datum& val);
	void unset(int p);

private:
	void** _data;
	int* _types;
	int* _cumDims;
	int _numDims;
	bool _soft;
	int _id;
	std::vector<int> _vdims;
	int _refcount;
	MemoryManager* _mm;

	void initBuffers();

	mcvfloat tofloat(int p);
	mcvint toint(int p);
	mcvuchar touchar(int p);
	std::string tosstring(int p);
};

}

#endif